﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq.Expressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using Dsl.Ped.Common.Commands;
using Dsl.Ped.Framework.Extensions;
using Dsl.Ped.Framework.Common;
using Dsl.Ped.Framework.Projections.CollectionProjections;
using Dsl.Ped.Framework.Projections.CollectionProjections.ExpandableCollection;
using Dsl.Ped.Framework.Projections.CollectionProjections.Vertical;

namespace Dsl.Ped.Framework.Projections
{
    /// <summary>
    /// Factory for the creation collection-based projections
    /// </summary>
    public class CollectionProjectionFactory : ProjectionFactoryBase
    {
        /// <summary>
        /// Initialize the projection control of the Collection-based projection (StackPanel, DockPanel,...)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="projectionHint"></param>
        /// <param name="objects"></param>
        /// <returns></returns>
        public static FrameworkElement InitializeCollectionProjection<T>(
            ICollectionProjectionHint<T> projectionHint, object source, string bindingPath = "Value") where T : ItemsControl, new()
        {
            var itemsProjection = InitializeProjection<T>(projectionHint, null);
            
            var binding = new Binding(bindingPath)
            {
                Source = source,
                Mode = BindingMode.TwoWay,
                StringFormat = null,
                ValidatesOnDataErrors = true,
                NotifyOnValidationError = true,

                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
            };

            itemsProjection.SetBinding(ItemsControl.ItemsSourceProperty, binding);

            return itemsProjection;
        }

        #region Vertical

        /// <summary>
        /// Gets the <see cref="VerticalProjection"/>
        /// </summary>
        /// <param name="source">Source object</param>
        /// <param name="propertyName">Name of the property which represents a collection</param>
        /// <param name="customConfiguration">Custom configuration that should be applied to the <see cref="VerticalProjection"/></param>
        /// <returns>Properly configured instance of a <see cref="VerticalProjection"/></returns>
        public static IProjection GetVerticalProjection(
            object source, string propertyName, Action<VerticalProjection> customConfiguration = null)
        {
            var projectionHint = new CollectionProjectionHint<VerticalProjection>(
                (verticalProjection, type) =>
                    {
                        if (customConfiguration != null)
                        {
                            customConfiguration(verticalProjection);
                        }

                        verticalProjection.BorderThickness = new Thickness(0); 
                    },
                vp => {});

            return new Projection() { View = InitializeCollectionProjection(projectionHint, source, propertyName) };
        }

        /// <summary>
        /// Gets the <see cref="VerticalProjection"/> of the collection of a specific type
        /// </summary>
        /// <typeparam name="T">Type of the objects in a collection</typeparam>
        /// <param name="source">Source object</param>
        /// <param name="memberExpression">Member expression that leads to a specific property in the <see cref="source"/></param>
        /// <param name="customConfiguration">Custom configuration that should be applied to the <see cref="VerticalProjection"/></param>
        /// <returns>Properly configured instance of a <see cref="VerticalProjection"/></returns>
        public static IProjection GetVerticalProjection<T>(
            object source, Expression<Func<T>> memberExpression, Action<VerticalProjection> customConfiguration = null)
        {
            var member = memberExpression.GetMember();
            return GetVerticalProjection(source, member.Name, customConfiguration);
        }

        /// <summary>
        /// Gets the <see cref="VerticalProjection"/> of the collection of a specific type
        /// </summary>
        /// <typeparam name="T">Type of the objects in a collection</typeparam>
        /// <param name="collection">Collections of the objects</param>
        /// <param name="itemsFilter">Predicate for the filtering of objects in a collection</param>
        /// <param name="customConfiguration">Custom configuration that should be applied to the <see cref="VerticalProjection"/></param>
        /// <returns>Properly configured instance of a <see cref="VerticalProjection"/></returns>
        public static IProjection GetVerticalProjection<T>(ObservableCollection<T> collection, Predicate<object> itemsFilter = null, Action<VerticalProjection> customConfiguration = null)
        {
            var verticalProjectionModel = new VerticalProjectionModel<T>(collection);
            return GetVerticalProjection(
                verticalProjectionModel,
                "Items",
                verticalProjection =>
                    {
                        if (customConfiguration != null) customConfiguration(verticalProjection);
                        verticalProjection.ItemsFilter = itemsFilter;
                    });
        }

        #endregion

        #region ExpandableCollection

        /// <summary>
        /// Gets the <see cref="ExpandableCollectionProjection"/>
        /// </summary>
        /// <param name="source">Source object</param>
        /// <param name="propertyName">Name of the property which represents a collection</param>
        /// <param name="customConfiguration">Custom configuration that should be applied to the <see cref="ExpandableCollectionProjection"/></param>
        /// <returns>Properly configured instance of a <see cref="ExpandableCollectionProjection"/></returns>
        public static IProjection GetExpandableCollectionProjection(object source, string propertyName, Action<ExpandableCollectionProjection> customConfiguration = null)
        {
            var projectionHint = new CollectionProjectionHint<ExpandableCollectionProjection>(
                (expandableCollectionProjection, type) =>
                    {
                        expandableCollectionProjection.BorderThickness = new Thickness(0);
                        if (customConfiguration != null) customConfiguration(expandableCollectionProjection);
                    },
                    ep => {});

            return new Projection() { View = InitializeCollectionProjection(projectionHint, source, propertyName) };
        }

        /// <summary>
        /// Gets the <see cref="ExpandableCollectionProjection"/>
        /// </summary>
        /// <typeparam name="T">Type of the objects in a colleciton</typeparam>
        /// <param name="source">Source object</param>
        /// <param name="memberExpression">Member expression that leads to a specific property in the <see cref="source"/></param>
        /// <param name="customConfiguration">Custom configuration that should be applied to the <see cref="ExpandableCollectionProjection"/></param>
        /// <returns>Properly configured instance of a <see cref="ExpandableCollectionProjection"/></returns>
        public static IProjection GetExpandableCollectionProjection<T>(
            object source, Expression<Func<IEnumerable<T>>> memberExpression, Action<ExpandableCollectionProjection> customConfiguration = null)
        {
            var member = memberExpression.GetMember();
            return GetExpandableCollectionProjection(source, member.Name, customConfiguration);
        }

        /// <summary>
        /// Gets the <see cref="ExpandableCollectionProjection"/>
        /// </summary>
        /// <typeparam name="T">Type of the objects in a colleciton</typeparam>
        /// <param name="source">Source object</param>
        /// <param name="memberExpression">Member expression that leads to a specific property in the <see cref="source"/></param>
        /// <param name="action">Action to be executed when the collection is expanding</param>
        /// <param name="customConfiguration">Custom configuration that should be applied to the <see cref="ExpandableCollectionProjection"/></param>
        /// <returns>Properly configured instance of a <see cref="ExpandableCollectionProjection"/></returns>
        public static IProjection GetExpandableCollectionProjection<T>(
            object source, Expression<Func<T>> memberExpression, Action<int> action, Action<ExpandableCollectionProjection> customConfiguration = null)
        {
            var member = memberExpression.GetMember();

            Action<ExpandableCollectionProjection> customConfigurationWithCommand = exp =>
                {
                    exp.Commands = new List<IMenuCommand>();

                    if (action != null)
                    {
                        exp.Commands.Add(new MenuCommand<int>("", action));
                    }

                    if (customConfiguration != null)
                    {
                        customConfiguration(exp);
                    }
                };

            return GetExpandableCollectionProjection(source, member.Name, customConfigurationWithCommand);
        }

        #endregion
    }
}
